// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/test/mock_keyboard_driver_win.h"

#include <stddef.h>
#include <string.h>

#include "base/logging.h"
#include "base/macros.h"
#include "content/test/mock_keyboard.h"

namespace content {

MockKeyboardDriverWin::MockKeyboardDriverWin()
{
    // Save the keyboard layout and status of the application.
    // This class changes the keyboard layout and status of this application.
    // This change may break succeeding tests. To prevent this possible break, we
    // should save the layout and status here to restore when this instance is
    // destroyed.
    original_keyboard_layout_ = GetKeyboardLayout(0);
    active_keyboard_layout_ = original_keyboard_layout_;
    GetKeyboardState(&original_keyboard_states_[0]);

    const UINT num_keyboard_layouts = GetKeyboardLayoutList(0, NULL);
    DCHECK(num_keyboard_layouts > 0);

    orig_keyboard_layouts_list_.resize(num_keyboard_layouts);
    GetKeyboardLayoutList(num_keyboard_layouts, &orig_keyboard_layouts_list_[0]);

    memset(&keyboard_states_[0], 0, sizeof(keyboard_states_));
}

MockKeyboardDriverWin::~MockKeyboardDriverWin()
{
    // Unload the keyboard-layout driver, restore the keyboard state, and reset
    // the keyboard layout for succeeding tests.
    MaybeUnloadActiveLayout();
    SetKeyboardState(&original_keyboard_states_[0]);
    ActivateKeyboardLayout(original_keyboard_layout_, KLF_RESET);
}

void MockKeyboardDriverWin::MaybeUnloadActiveLayout()
{
    // Workaround for http://crbug.com/12093
    // Only unload a keyboard layout if it was loaded by this mock driver.
    // Contrary to the documentation on MSDN unloading a keyboard layout
    // previously loaded by the system causes that layout to stop working.
    // We have confirmation of this behavior on XP & Vista.
    for (size_t i = 0; i < orig_keyboard_layouts_list_.size(); ++i) {
        if (orig_keyboard_layouts_list_[i] == active_keyboard_layout_)
            return;
    }

    // If we got here, this keyboard layout wasn't loaded by the system so it's
    // safe to unload it ourselve's.
    UnloadKeyboardLayout(active_keyboard_layout_);
    active_keyboard_layout_ = original_keyboard_layout_;
}

bool MockKeyboardDriverWin::SetLayout(int layout)
{
    // Unload the current keyboard-layout driver and load a new keyboard-layout
    // driver for mapping a virtual key-code to a Unicode character.
    MaybeUnloadActiveLayout();

    // Scan the mapping table and retrieve a Language ID for the input layout.
    // Load the keyboard-layout driver when we find a Language ID.
    // This Language IDs are copied from the registry
    //   "HKLM\SYSTEM\CurrentControlSet\Control\Keyboard layouts".
    // TODO(hbono): Add more keyboard-layout drivers.
    static const struct {
        const wchar_t* language;
        MockKeyboard::Layout keyboard_layout;
    } kLanguageIDs[] = {
        { L"00000401", MockKeyboard::LAYOUT_ARABIC },
        { L"00000402", MockKeyboard::LAYOUT_BULGARIAN },
        { L"00000404", MockKeyboard::LAYOUT_CHINESE_TRADITIONAL },
        { L"00000405", MockKeyboard::LAYOUT_CZECH },
        { L"00000406", MockKeyboard::LAYOUT_DANISH },
        { L"00000407", MockKeyboard::LAYOUT_GERMAN },
        { L"00000408", MockKeyboard::LAYOUT_GREEK },
        { L"00000409", MockKeyboard::LAYOUT_UNITED_STATES },
        { L"0000040a", MockKeyboard::LAYOUT_SPANISH },
        { L"0000040b", MockKeyboard::LAYOUT_FINNISH },
        { L"0000040c", MockKeyboard::LAYOUT_FRENCH },
        { L"0000040d", MockKeyboard::LAYOUT_HEBREW },
        { L"0000040e", MockKeyboard::LAYOUT_HUNGARIAN },
        { L"00000410", MockKeyboard::LAYOUT_ITALIAN },
        { L"00000411", MockKeyboard::LAYOUT_JAPANESE },
        { L"00000412", MockKeyboard::LAYOUT_KOREAN },
        { L"00000415", MockKeyboard::LAYOUT_POLISH },
        { L"00000416", MockKeyboard::LAYOUT_PORTUGUESE_BRAZILIAN },
        { L"00000418", MockKeyboard::LAYOUT_ROMANIAN },
        { L"00000419", MockKeyboard::LAYOUT_RUSSIAN },
        { L"0000041a", MockKeyboard::LAYOUT_CROATIAN },
        { L"0000041b", MockKeyboard::LAYOUT_SLOVAK },
        { L"0000041e", MockKeyboard::LAYOUT_THAI },
        { L"0000041d", MockKeyboard::LAYOUT_SWEDISH },
        { L"0000041f", MockKeyboard::LAYOUT_TURKISH_Q },
        { L"0000042a", MockKeyboard::LAYOUT_VIETNAMESE },
        { L"00000439", MockKeyboard::LAYOUT_DEVANAGARI_INSCRIPT },
        { L"00000816", MockKeyboard::LAYOUT_PORTUGUESE },
        { L"00001409", MockKeyboard::LAYOUT_UNITED_STATES_DVORAK },
        { L"00001009", MockKeyboard::LAYOUT_CANADIAN_FRENCH },
    };

    for (size_t i = 0; i < arraysize(kLanguageIDs); ++i) {
        if (layout == kLanguageIDs[i].keyboard_layout) {
            HKL new_keyboard_layout = LoadKeyboardLayout(kLanguageIDs[i].language,
                KLF_ACTIVATE);
            // loaded_keyboard_layout_ must always have a valid keyboard handle
            // so we only assign upon success.
            if (new_keyboard_layout) {
                active_keyboard_layout_ = new_keyboard_layout;
                return true;
            }

            return false;
        }
    }

    // Return false if there are not any matching drivers.
    return false;
}

bool MockKeyboardDriverWin::SetModifiers(int modifiers)
{
    // Over-write the keyboard status with our modifier-key status.
    // WebInputEventFactory::keyboardEvent() uses GetKeyState() to retrive
    // modifier-key status. So, we update the modifier-key status with this
    // SetKeyboardState() call before creating NativeWebKeyboardEvent
    // instances.
    memset(&keyboard_states_[0], 0, sizeof(keyboard_states_));
    static const struct {
        int key_code;
        int mask;
    } kModifierMasks[] = {
        { VK_SHIFT, MockKeyboard::LEFT_SHIFT | MockKeyboard::RIGHT_SHIFT },
        { VK_CONTROL, MockKeyboard::LEFT_CONTROL | MockKeyboard::RIGHT_CONTROL },
        { VK_MENU, MockKeyboard::LEFT_ALT | MockKeyboard::RIGHT_ALT },
        { VK_LSHIFT, MockKeyboard::LEFT_SHIFT },
        { VK_LCONTROL, MockKeyboard::LEFT_CONTROL },
        { VK_LMENU, MockKeyboard::LEFT_ALT },
        { VK_RSHIFT, MockKeyboard::RIGHT_SHIFT },
        { VK_RCONTROL, MockKeyboard::RIGHT_CONTROL },
        { VK_RMENU, MockKeyboard::RIGHT_ALT },
    };
    for (size_t i = 0; i < arraysize(kModifierMasks); ++i) {
        const int kKeyDownMask = 0x80;
        if (modifiers & kModifierMasks[i].mask)
            keyboard_states_[kModifierMasks[i].key_code] = kKeyDownMask;
    }
    SetKeyboardState(&keyboard_states_[0]);

    return true;
}

int MockKeyboardDriverWin::GetCharacters(int key_code,
    std::wstring* output)
{
    // Retrieve Unicode characters composed from the input key-code and
    // the mofifiers.
    CHECK(output);
    wchar_t code[16];
    int length = ToUnicodeEx(key_code, MapVirtualKey(key_code, 0),
        &keyboard_states_[0], &code[0], arraysize(code), 0,
        active_keyboard_layout_);
    if (length > 0)
        output->assign(code);
    return length;
}

} // namespace content
